const 斷言 & infer 關鍵字


Posted by TempuraEngineer on 2023-11-17

目錄


const斷言是什麼

(2024/4/30更新)

const斷言(assertion)是一種型別斷言,它可以告訴Typescript限縮變數的型別、把變數變成readonly,寫immutable code時會用到

使用as const會把型別變成literal type

literal
adjective
The literal meaning of a word is its original, basic meaning.

literal type是一種鎖死的型別,簡單說就是你詳細定義「這個字串(數值)是什麼」、「這個陣列內的元素是什麼」、「這個物件內有什麼屬性、屬性值是什麼」

const斷言和const宣告的差別在於並不是用於宣告變數,或者限縮型別,兩者使用的場景並不完全一樣

 // 型別是string;
let fruit = 'apple';

 // 型別是apple;
let fruit2 = 'apple' as const;
const fruit3 = 'apple';
let arr = [10, 20]; // number[]
const  arr2 = [10, 20] as const; // readonly [10, 20]
const  arr3 = arr2; // readonly [10, 20]

arr.push(30);

// Property 'push' does not exist on type 'readonly [10, 20]'
arr2.push(30);

// Property 'push' does not exist on type 'readonly [10, 20]'
// 傳址,且其reference已經被as const鎖死型別,所以不能push
arr3.push(30);
const obj = {
    a:1,
};
// {
//     a: number;
// }

// 使用了as const以後物件的型別完全被鎖死,所有屬性變readonly(immutable)
const obj2 = {
    a:1,
} as const;
// {
//     readonly a: 1;
// }

// Property 'b' does not exist on type '{ a: number; }'.
obj.b = 2;
// 型別沒有被完全鎖死,所以可以修改屬性值
obj.a = 20;

Property 'b' does not exist on type '{ readonly a: 1; }'
obj2.b = 2;
Cannot assign to 'a' because it is a read-only property.
obj2.a = 20;

在一些需要enum的場景也可以使用const斷言代替

const mainDishes = {
    beef:'steak',
    pork:'pepper pork',
    lamb:'roasted lamb',
    fish:'sashimi',
    shrimp:'curry shirmp',
    carb:'butter crab',
} as const;
// {
//     readonly beef: "steak";
//     readonly pork: "pepper pork";
//     readonly lamb: "roasted lamb";
//     readonly fish: "sashimi";
//     readonly shrimp: "curry shirmp";
//     readonly carb: "butter crab";
// }

// Cannot assign to 'beef' because it is a read-only property.
mainDishes.beef = 'roasted beef';


const斷言限制

1.只能用於直接賦值宣告、簡單的表達式

```js
// bad
// A 'const' assertion can only be applied to a to a string, number, boolean, array, or object literal.
const num = 1 + 2 as const;
const day = new Date('2023-11-13') as const;
const set = new Set([1,2,3]) as const;
const maxNum = Math.max(...[10,20,30]) as const;

// ok
const num = 1 as const;
const weekends = ['Sunday', 'Saturday'] as const;
```

2.如果物件的屬性有reference,那const斷言不會對那個屬性生效

```js
let arr = ['chocolate']; // string[]
let arr2 = ['coffe'] as const; // let arr2 = ['coffe'] as const;

let person = {
  name: "Derek",
  favorite: arr,
  hate: arr2,
  hobby: ['gardening'],

} as const;
// {
//     readonly name: "Derek";
//     readonly favorite: string[];
//     readonly hobby: readonly ["gardening"];
// }

// Property 'push' does not exist on type 'readonly ["gardening"]'.
person.hobby.push('swimming');

// 可以push的原因是arr並沒有被用as const鎖死型別,所以可以修改
person.favorite.push('strawberry');

// Cannot assign to 'favorite' because it is a read-only property.
// 不可以重新將favorite屬性賦值,因為person被用as const鎖死型別
person.favorite = ['apple'];

// Property 'push' does not exist on type 'readonly ["coffe"]'.
// 不可以push是因為arr2被用as const鎖死型別
person.hate.push('milk');
```


infer關鍵字是什麼

使用infer關鍵字(keyword)可以讓TS自動從推斷型別參數推斷出一個型別,並傳給變數,可以在限縮型別的同時讓型別變得更彈性

用於條件型別(conditional type),能解決深度巢狀的條件型別(a? b : c? d : e? f : g)

enum Desserts {
    Cake = 'chocolate cake',
    Jelly = 'organge jelly',
    IceCream = 'macha ice cream'
}

// 攤平陣列取得element的型別
type Flatten<Type> = Type extends (infer Item)[] ? Item : Type;

// Desserts[]是型別參數
// Type被extends限制必須要是陣列,如果Type是陣列,TS會從它推斷出其中的元素的型別,即Desserts,並將Desserts賦予給Item變數
type Sweets = Flatten<Desserts[]>; // Desserts

用於推斷return value型別、parameter型別

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return ? Return : never;

type Num = GetReturnType<() => number>;

// 相當於
type Num2 = ReturnType<() => number>;
type GetParameterType<Type> = Type extends (arg: infer Args) => any ? Args : Type;

type Args = GetParameterType<(a:number, b: string) => void>

// 相當於
type Params = Parameters<(a:number, b: string) => void>;

ComponentProps的實作

React也提供了util type,只要傳component給它就可以取得其props型別

import {ComponentProps} from 'react';

type PropsOfMyButton = ComponentProps<typeof MyButton>; // MyButtonProps

type PropsOfButton = ComponentProps<"button">; // ClassAttributes<HTMLButtonElement> & ButtonHTMLAttributes<HTMLButtonElement>

type Try = ComponentProps<123>; // {}
// 原始碼
type ComponentProps<
  T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
> = T extends JSXElementConstructor<infer P> ? P : 
      T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] : {};

JSX.IntrinsicElements是在 JSX 中可以使用的HTML 元素 (ex: button、span)
JSXElementConstructor則是在 JSX 中可以使用的 React 組件 (ex: function component、class component)

以上的程式碼的意思是,如果傳入的型別參數是組件,那就回傳其props。如果不是的話,檢查是否為HTML元素,是的話就回傳該HTML元素的所有屬性 (ex: style、type、name、value...),如果不是的話回傳{}


infer關鍵字限制

不是conditional type時,infer不能寫在extend後(型別參數的限制子句)

// bad
// 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
type Item<T extends (infer U)[]> = U;

參考資料

TS - const assertions
TS - Inferring Within Conditional Types
TS - Literal Types
TS - readonly Tuple Types


#TypeScript #const assertion #infer keyword #conditional type







Related Posts

Cacti 安裝

Cacti 安裝

redis 套件的 Property 'on' does not exist on type 'RedisClientType'

redis 套件的 Property 'on' does not exist on type 'RedisClientType'

APIFlask 初始化專案

APIFlask 初始化專案


Comments